#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>


#define KEY_CNT 1
#define NAME "key"

enum key_status{
    KEY_PRESS=0,
    KEY_RELEASE,
    KEY_KEEP,
};

struct key_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    // int major;
    // int minor;
    // struct device_node *nd;

    int key_gpio;
    int irq_num;
    struct timer_list timer;
    // spinlock_t spinlock;
    wait_queue_head_t r_wait;
};

static struct key_dev key;
static atomic_t status;


static int key_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static int key_release(struct inode *inode, struct file *filp)
{
    return 0;    
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;

    if(filp->f_flags & O_NONBLOCK){
        if(atomic_read(&status)==KEY_KEEP){
            return -EAGAIN;
        }
    }
    else {
        ret=wait_event_interruptible(key.r_wait, atomic_read(&status)!=KEY_KEEP);
    }

    ret=copy_to_user(buf, &status, sizeof(int));
    atomic_set(&status, KEY_KEEP);

    return ret;
}

static unsigned int key_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask=0;

    printk(KERN_INFO "exec: key_poll\n");
    poll_wait(filp, &key.r_wait, wait);
    if(atomic_read(&status)!=KEY_KEEP){
        mask=POLLIN|POLLRDNORM;
    }
    return mask;
}

static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static struct file_operations key_fops={
    .owner=THIS_MODULE,
    .open=key_open,
    .read=key_read,
    .write=key_write,
    .release=key_release,
    .poll=key_poll,
};


static void key_timer_function(unsigned long arg)
{
    static int last_val=1;
    int current_val;

    printk(KERN_INFO "exec: timeout function\n");
    current_val=gpio_get_value(key.key_gpio);
    if(current_val==0 && last_val==1){
        atomic_set(&status, KEY_PRESS);
        wake_up_interruptible(&key.r_wait);
    }
    else if(current_val==1 && last_val==0){
        atomic_set(&status, KEY_RELEASE);
        wake_up_interruptible(&key.r_wait);
    }
    else {
        atomic_set(&status, KEY_KEEP);
    }

    last_val=current_val;
}

static irqreturn_t key_interrupt(int irq, void *arg)
{
    mod_timer(&key.timer, jiffies+msecs_to_jiffies(15));
    return IRQ_HANDLED;
}

static int key_parse_dt(void)
{
    int ret;
    const char *str;
    struct device_node *nd;

    /*0*/
    nd=of_find_node_by_path("/key");
    ret=of_property_read_string(nd, "status", &str);
    if(strcmp(str, "okay")){
        return -EINVAL;
    }

    ret=of_property_read_string(nd, "compatible", &str);
    if(strcmp(str, "alientek,key")){
        return -EINVAL;
    }

    printk(KERN_INFO "key device matching successful!\n");

    /*1*/
    key.key_gpio=of_get_named_gpio(nd, "key-gpio", 0);
    printk(KERN_INFO "key: key-gpio num=%d\n", key.key_gpio);

    key.irq_num=irq_of_parse_and_map(nd,0);
    printk(KERN_INFO "key: key-irq num=%d\n", key.irq_num);
    if(key.irq_num==0){
        return -EINVAL;
    }

    return 0;
}

static int key_gpio_init(void)
{
    unsigned long irq_flags;
    int ret;

    ret=gpio_request(key.key_gpio, "KEY-GPIO");
    gpio_direction_input(key.key_gpio);

    irq_flags=irq_get_trigger_type(key.irq_num);
    if(irq_flags==IRQF_TRIGGER_NONE){
        irq_flags=IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
    }

    ret = request_irq(key.irq_num, key_interrupt, irq_flags, "PS Key0 IRQ", NULL);

    return 0;
}

static int __init mykey_init(void)
{
    int ret;

    init_waitqueue_head(&key.r_wait);

    ret=key_parse_dt();
    ret=key_gpio_init();

    ret=alloc_chrdev_region(&key.devid, 0, KEY_CNT, NAME);

    /*8*/
    key.cdev.owner=THIS_MODULE;
    cdev_init(&key.cdev, &key_fops);
    ret=cdev_add(&key.cdev, key.devid, KEY_CNT);

    /*9*/
    key.class=class_create(THIS_MODULE, NAME);
    key.device=device_create(key.class,
                                    NULL,
                                    key.devid,
                                    NULL,
                                    NAME);
    
    atomic_set(&status, KEY_KEEP);
    init_timer(&key.timer);
    key.timer.function=key_timer_function;

    return 0;
}

static void __exit mykey_exit(void)
{
    del_timer_sync(&key.timer);
    device_destroy(key.class, key.devid);
    class_destroy(key.class);
    cdev_del(&key.cdev);
    unregister_chrdev_region(key.devid, KEY_CNT);
    gpio_free(key.key_gpio);

    printk("key_exit()\n");
}

module_init(mykey_init);
module_exit(mykey_exit);

MODULE_AUTHOR("xujianchao");
MODULE_DESCRIPTION("xujianchao ZYNQ GPIO KEY Driver");
MODULE_LICENSE("GPL");
